﻿using System;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;

using Autofac;
using Autofac.Core;
using Autofac.Extensions.DependencyInjection;

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyModel;
using Microsoft.Extensions.Hosting;

namespace Dotnet3WebApiDemo
{
    public static class AutofacExtensions
    {
        public static IHostBuilder UseAutofac(this IHostBuilder host)
        {
            host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
            host.ConfigureContainer<ContainerBuilder>(container =>
            {
                PropertySelector propertySelector = new PropertySelector();
                var libs = DependencyContext.Default.CompileLibraries.Where(lib => lib.Serviceable == false && lib.Type == "project").Select(lib => lib.Name).ToList();

                var assemblies = libs.Select(lib => AssemblyLoadContext.Default.LoadFromAssemblyName(new System.Reflection.AssemblyName(lib))).ToArray();

                container
                    .RegisterAssemblyTypes(assemblies)
                    .Where(type => type.IsAbstract == false && type.IsAssignableTo<ISingletonIocTag>())
                    .AsSelf()
                    .AsImplementedInterfaces()
                    .SingleInstance()
                    .PropertiesAutowired(propertySelector, true);

                container
                    .RegisterAssemblyTypes(assemblies)
                    .Where(type => type.IsAbstract == false && type.IsAssignableTo<IPerRequestIocTag>())
                    .AsSelf()
                    .AsImplementedInterfaces()
                    .InstancePerRequest("transaction")
                    .PropertiesAutowired(new PropertySelector(), true);

                container
                    .RegisterAssemblyTypes(assemblies)
                    .Where(type => type.IsAbstract == false && type.IsAssignableTo<IScopedIocTag>())
                    .AsSelf()
                    .AsImplementedInterfaces()
                    .InstancePerLifetimeScope()
                    .PropertiesAutowired(propertySelector, true);

                container
                    .RegisterAssemblyTypes(assemblies)
                    .Where(type => type.IsAbstract == false && type.IsAssignableTo<ITransientIocTag>())
                    .AsSelf()
                    .AsImplementedInterfaces()
                    .InstancePerDependency()
                    .PropertiesAutowired(propertySelector, true);

                container
                    .RegisterAssemblyTypes(assemblies)
                    .Where(type => type.IsAbstract == false && type.IsAssignableTo<ControllerBase>())
                    .AsSelf()
                    .InstancePerDependency()
                    .PropertiesAutowired(propertySelector, true);
            });

            return host;
        }
    }

    public interface ISingletonIocTag
    { }
    public interface IPerRequestIocTag
    { }
    public interface IScopedIocTag
    { }
    public interface ITransientIocTag
    { }

    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
    public class AutowaredAttribute : Attribute { }

    public class PropertySelector : IPropertySelector
    {
        public bool InjectProperty(PropertyInfo propertyInfo, object instance)
        {
            if (propertyInfo.CanWrite == false) return false;

            BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public;
            if (instance.GetType().GetProperty(propertyInfo.Name, bindingFlags) != null) return true;

            return propertyInfo.IsDefined(typeof(AutowaredAttribute));
        }
    }
}
